package com.such.kit.file;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import com.such.kit.Callable;
import com.such.kit.datastructure.tree.TreeCommons;
import com.such.kit.datastructure.tree.bean.Node;
import com.such.kit.datastructure.tree.bean.Tree;
import com.such.kit.file.bean.FileInfo;
import com.such.kit.file.bean.FileOrder;
import com.such.kit.file.exception.FileExistException;
import com.such.kit.file.support.FileFilter;
import com.such.kit.stream.StreamCommons;
import com.such.kit.string.StringCommons;
import com.such.kit.validate.ValidateCommons;

/**
 * @author SUCH
 * <pre>
 * 通用文件处理工具类
 * </pre>
 */
public class FileCommons {

	/** compatSeparator：兼容路径间隔符 */
	public static final String COMPAT_SEPARATOR = "/";

	/** copyFix：默认的同目录文件复制后缀 */
	private static String copyFix = "_copy";
	/** treeProcessor：以树形结构列表文件夹时使用的节点处理器 */
	private static Callable<FileInfo, String[]> treeProcessor = new Callable<FileInfo, String[]>() {
		@Override
		public String[] call(FileInfo fi) throws Exception {
			return new String[] {fi.getPath(), fi.getParentPath(), fi.getName()};
		}
	};

	/**
	 * <pre>
	 * 重命名文件
	 * </pre>
	 * @param filePath 文件绝对路径
	 * @param newName 新的文件名称
	 */
	public static void rename(String filePath, String newName) {
		filePath = changeCompatSeparator(filePath);
		File file = new File(filePath);
		if (file.exists()) {
			filePath = cutLastPathNode(filePath);
			file.renameTo(new File(StringCommons.merger(filePath, COMPAT_SEPARATOR, newName)));
		}
	}

	/**
	 * <pre>
	 * 创建文件夹
	 * </pre>
	 * @param folderPath 文件夹绝对路径
	 */
	public static void createFolder(String folderPath) {
		folderPath = changeCompatSeparator(folderPath);
		File folder = new File(folderPath);
		if (!folder.exists()) {
			folder.mkdirs();
		}
	}

	/**
	 * <pre>
	 * 删除文件夹
	 * 级联删除文件夹下的文件、文件夹
	 * </pre>
	 * @param folderPath 文件夹绝对路径
	 */
	public static void deleteFolder(String folderPath) {
		folderPath = changeCompatSeparator(folderPath);
		deleteFiles(folderPath, true);
		File folder = new File(folderPath);
		if (folder.exists() && folder.isDirectory()) {
			folder.delete();
		}
	}

	/**
	 * <pre>
	 * [参考 {@link #copyFolder(String, String, String)}]
	 * 
	 * targetName 的默认值为空
	 * </pre>
	 */
	public static void copyFolder(String folderPath, String targetPath)
			throws IOException {
		copyFolder(folderPath, targetPath, null);
	}

	/**
	 * <pre>
	 * 复制文件夹
	 * 级联复制文件夹下的文件、文件夹
	 * 
	 * 当 folderPath 与 targetPath 相等（在同目录复制），系统自动在复制文件夹前增加后缀 {@link #copyFix}
	 * </pre>
	 * @param folderPath 文件夹绝对路径
	 * @param targetPath 目标绝对路径
	 * @param targetName 目标名称 [可空]
	 * @throws IOException
	 */
	public static void copyFolder(String folderPath, String targetPath, String targetName)
			throws IOException {
		folderPath = changeCompatSeparator(folderPath);
		targetPath = changeCompatSeparator(targetPath);
		if (ValidateCommons.isNotEmpty(folderPath) && ValidateCommons.isNotEmpty(targetPath)) {
			File folder = new File(folderPath);
			if (folder.exists() && folder.isDirectory()) {
				if (ValidateCommons.isEmpty(targetName)) {
					targetName = folder.getName();
				}
				targetPath = StringCommons.merger(targetPath, COMPAT_SEPARATOR, targetName);
				if (folderPath.equals(targetPath)) {
					targetPath = addSuffix(targetPath, copyFix);
				}
				createFolder(targetPath);
				File[] fileList = folder.listFiles();
				if (ValidateCommons.isNotEmpty(fileList)) {
					for (File file : fileList) {
						String tempPath = file.getPath();
						if (file.isFile()) {
							String childPath = StringCommons.merger(targetPath, COMPAT_SEPARATOR, file.getName());
							copyFile(tempPath, childPath);
						} else if (file.isDirectory()) {
							copyFolder(tempPath, targetPath);
						}
					}
				}
			}
		}
	}

	/**
	 * <pre>
	 * 剪切文件夹
	 * 级联剪切文件夹下的文件、文件夹
	 * </pre>
	 * @param folderPath 文件夹绝对路径
	 * @param targetPath 目标绝对路径
	 * @throws IOException
	 */
	public static void cutFolder(String folderPath, String targetPath)
			throws IOException {
		copyFolder(folderPath, targetPath);
		deleteFolder(folderPath);
	}

	/**
	 * <pre>
	 * 打包（zip）输出文件夹
	 * 级联输出文件夹下的文件、文件夹
	 * </pre>
	 * @param folderPath 文件夹绝对路径
	 * @param zipOut zip 输出流
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public static void outputFolder(String folderPath, ZipArchiveOutputStream zipOut)
			throws FileNotFoundException, IOException {
		outputFolder(folderPath, zipOut, "");
	}

	/**
	 * <pre>
	 * 打包（zip）输出文件夹
	 * 级联输出文件夹下的文件、文件夹
	 * </pre>
	 * @param folderPath 文件夹绝对路径
	 * @param zipOut zip 输出流
	 * @param parentEntry 条目上级路径
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public static void outputFolder(String folderPath, ZipArchiveOutputStream zipOut, String parentEntry)
			throws FileNotFoundException, IOException {
		outputFiles(listFolder(folderPath), zipOut, parentEntry);
	}

	/**
	 * <pre>
	 * [参考 {@link #listFolder(String, String, boolean, FileFilter)}]
	 * 
	 * orderMode 的默认值为 {@link FileOrder#ORDER_MODE_NAME}
	 * isDesc 的默认值为 false
	 * fileFilter 的默认值为 null
	 * </pre>
	 */
	public static List<FileInfo> listFolder(String folderPath) {
		return listFolder(folderPath, FileOrder.ORDER_MODE_NAME, false, null);
	}

	/**
	 * <pre>
	 * [参考 {@link #listFolder(String, String, boolean, FileFilter)}]
	 * 
	 * orderMode 的默认值为 {@link FileOrder#ORDER_MODE_NAME}
	 * isDesc 的默认值为 false
	 * </pre>
	 */
	public static List<FileInfo> listFolder(String folderPath, FileFilter fileFilter) {
		return listFolder(folderPath, FileOrder.ORDER_MODE_NAME, false, fileFilter);
	}

	/**
	 * <pre>
	 * 列表文件夹
	 * </pre>
	 * @param folderPath 文件夹绝对路径
	 * @param orderMode 排序模式
	 * @param isDesc 是否使用降序排列
	 * @param fileFilter 文件列表过滤器 [可空 为空时不做过滤]
	 * @return 文件列表
	 */
	public static List<FileInfo> listFolder(String folderPath, String orderMode, boolean isDesc, FileFilter fileFilter) {
		folderPath = changeCompatSeparator(folderPath);
		List<FileInfo> fileInfoList = new ArrayList<FileInfo>();
		File dir = new File(folderPath);
		if (dir.exists() && dir.isDirectory()) {
			File[] files = dir.listFiles();
			if (ValidateCommons.isNotEmpty(files)) {
				for (File file : files) {
					FileInfo fi = new FileInfo(file);
					if (ValidateCommons.isEmpty(fileFilter) || fileFilter.accept(fi)) {
						fileInfoList.add(fi);
					}
				}
				Collections.sort(fileInfoList, new FileOrder(orderMode, isDesc));
			}
		}
		return fileInfoList;
	}

	/**
	 * <pre>
	 * [参考 {@link #listFolder(String, String, boolean, FileFilter, Callable)}]
	 * 
	 * orderMode 的默认值为 {@link FileOrder#ORDER_MODE_NAME}
	 * isDesc 的默认值为 false
	 * fileFilter 的默认值为 null
	 * </pre>
	 */
	public static void listFolder(String folderPath, Callable<FileInfo, Void> processor)
			throws Exception {
		listFolder(folderPath, FileOrder.ORDER_MODE_NAME, false, null, processor);
	}

	/**
	 * <pre>
	 * [参考 {@link #listFolder(String, String, boolean, FileFilter, Callable)}]
	 * 
	 * orderMode 的默认值为 {@link FileOrder#ORDER_MODE_NAME}
	 * isDesc 的默认值为 false
	 * </pre>
	 */
	public static void listFolder(String folderPath, FileFilter fileFilter, Callable<FileInfo, Void> processor)
			throws Exception {
		listFolder(folderPath, FileOrder.ORDER_MODE_NAME, false, fileFilter, processor);
	}

	/**
	 * <pre>
	 * 列表文件夹
	 * 级联列表文件夹下的文件、文件夹
	 * </pre>
	 * @param folderPath 文件夹绝对路径
	 * @param orderMode 排序模式
	 * @param isDesc 是否使用降序排列
	 * @param fileFilter 文件列表过滤器 [可空 为空时不做过滤]
	 * @param processor 文件处理回调
	 * @throws Exception
	 */
	public static void listFolder(String folderPath, String orderMode, boolean isDesc, FileFilter fileFilter, Callable<FileInfo, Void> processor)
			throws Exception {
		List<FileInfo> fileInfoList = listFolder(folderPath, orderMode, isDesc, fileFilter);
		for (FileInfo fi : fileInfoList) {
			processor.call(fi);
			if (fi.isDirectory()) {
				listFolder(fi.getPath(), orderMode, isDesc, fileFilter, processor);
			}
		}
	}

	/**
	 * <pre>
	 * [参考 {@link #listFolderTree(String, String, boolean, FileFilter, Tree)}]
	 * 
	 * orderMode 的默认值为 {@link FileOrder#ORDER_MODE_NAME}
	 * isDesc 的默认值为 false
	 * fileFilter 的默认值为 null
	 * </pre>
	 */
	public static Tree<FileInfo> listFolderTree(String folderPath, Tree<FileInfo> tree)
			throws Exception {
		return listFolderTree(folderPath, FileOrder.ORDER_MODE_NAME, false, null, tree);
	}

	/**
	 * <pre>
	 * [参考 {@link #listFolderTree(String, String, boolean, FileFilter, Tree)}]
	 * 
	 * orderMode 的默认值为 {@link FileOrder#ORDER_MODE_NAME}
	 * isDesc 的默认值为 false
	 * </pre>
	 */
	public static Tree<FileInfo> listFolderTree(String folderPath, FileFilter fileFilter, Tree<FileInfo> tree)
			throws Exception {
		return listFolderTree(folderPath, FileOrder.ORDER_MODE_NAME, false, fileFilter, tree);
	}

	/**
	 * <pre>
	 * 以树形结构列表文件夹
	 * 级联列表文件夹下的文件、文件夹
	 * 
	 * [警告] {@link Tree} 的默认 {@link Node#idPath} 分隔符为 {@link #COMPAT_SEPARATOR}，此分隔符不适用于文件路径，建议使用符号 >
	 * </pre>
	 * @param folderPath 文件夹绝对路径
	 * @param orderMode 排序模式
	 * @param isDesc 是否使用降序排列
	 * @param fileFilter 文件列表过滤器 [可空 为空时不做过滤]
	 * @param tree 树 [可空 为空时默认创建新树]
	 * @return 树
	 * @throws Exception
	 */
	public static Tree<FileInfo> listFolderTree(String folderPath, String orderMode, boolean isDesc, FileFilter fileFilter, Tree<FileInfo> tree)
			throws Exception {
		if (ValidateCommons.isEmpty(tree)) {
			tree = new Tree<FileInfo>(">");
		}
		folderPath = changeCompatSeparator(folderPath);
		List<FileInfo> fileInfoList = listFolder(folderPath, orderMode, isDesc, fileFilter);
		if (ValidateCommons.isNotEmpty(fileInfoList)) {
			tree.add(TreeCommons.createNodes(fileInfoList, treeProcessor));
			for (FileInfo fileInfo : fileInfoList) {
				tree = listFolderTree(fileInfo.getPath(), orderMode, isDesc, fileFilter, tree);
			}
		}
		return tree;
	}

	/**
	 * <pre>
	 * 读取文件内容
	 * 
	 * [警告] 此方法只能读取文本类文件
	 * </pre>
	 * @param filePath 文件绝对路径
	 * @return 文件内容
	 * @throws IOException
	 */
	public static String readFile(String filePath)
			throws IOException {
		return readFile(filePath, null);
	}

	/**
	 * <pre>
	 * 读取文件内容
	 * 
	 * [警告] 此方法只能读取文本类文件
	 * </pre>
	 * @param filePath 文件绝对路径
	 * @param charset 文件编码
	 * @return 文件内容
	 * @throws IOException
	 */
	public static String readFile(String filePath, String charset)
			throws IOException {
		String text = null;
		filePath = changeCompatSeparator(filePath);
		File file = new File(filePath);
		if (file.exists()) {
			if (ValidateCommons.isEmpty(charset)) {
				text = StreamCommons.textReader(new InputStreamReader(new FileInputStream(file)));
			} else {
				text = StreamCommons.textReader(new InputStreamReader(new FileInputStream(file), charset));
			}
		}
		return text;
	}

	/**
	 * <pre>
	 * 创建文件
	 * 自动创建文件夹
	 * </pre>
	 * @param in 输入流
	 * @param outPath 输出绝对路径
	 * @throws FileExistException
	 * @throws IOException
	 */
	public static void createFile(InputStream in, String outPath)
			throws FileExistException, IOException {
		outPath = changeCompatSeparator(outPath);
		File file = new File(outPath);
		if (file.exists()) {
			throw new FileExistException(outPath);
		}
		createFolder(cutLastPathNode(outPath));
		FileOutputStream out = new FileOutputStream(outPath);
		StreamCommons.binaryTransport(in, out);
	}

	/**
	 * <pre>
	 * 创建文件
	 * 自动创建文件夹
	 * </pre>
	 * @param r 读入流
	 * @param outPath 输出绝对路径
	 * @throws FileExistException
	 * @throws IOException
	 */
	public static void createFile(InputStreamReader r, String outPath)
			throws FileExistException, IOException {
		outPath = changeCompatSeparator(outPath);
		File file = new File(outPath);
		if (file.exists()) {
			throw new FileExistException(outPath);
		}
		createFolder(cutLastPathNode(outPath));
		OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(outPath), r.getEncoding());
		StreamCommons.textTransport(r, w);
	}

	/**
	 * <pre>
	 * 删除文件
	 * </pre>
	 * @param filePath 文件绝对路径
	 */
	public static void deleteFile(String filePath) {
		filePath = changeCompatSeparator(filePath);
		File file = new File(filePath);
		if (file.exists() && file.isFile()) {
			file.delete();
		}
	}

	/**
	 * <pre>
	 * 删除文件夹下文件
	 * 
	 * [参考 {@link #deleteFiles(List, boolean)}]
	 * </pre>
	 * @param folderPath 文件夹绝对路径
	 * @param containFolder 是否删除文件夹
	 */
	public static void deleteFiles(String folderPath, boolean containFolder) {
		folderPath = changeCompatSeparator(folderPath);
		File file = new File(folderPath);
		if (file.exists() && file.isDirectory()) {
			deleteFiles(listFolder(folderPath), containFolder);
		}
	}

	/**
	 * <pre>
	 * 删除文件列表
	 * containFolder 为 false 时只会删除文件
	 * containFolder 为 true 时除了删除文件外，还会删除文件夹并级联删除文件夹下的文件、文件夹
	 * </pre>
	 * @param fileInfoList 文件列表
	 * @param containFolder 是否删除文件夹
	 */
	public static void deleteFiles(List<FileInfo> fileInfoList, boolean containFolder) {
		if (ValidateCommons.isNotEmpty(fileInfoList)) {
			for (int i = 0; i < fileInfoList.size(); i++) {
				FileInfo fi = fileInfoList.get(i);
				String path = fi.getPath();
				if (fi.isDirectory() && containFolder) {
					deleteFolder(path);
				} else {
					deleteFile(path);
				}
			}
		}
	}

	/**
	 * <pre>
	 * 复制文件
	 * 
	 * 当 filePath 与 targetPath 相等（在同目录复制），系统自动在复制文件前增加后缀 {@link #copyFix}
	 * </pre>
	 * @param filePath 文件绝对路径
	 * @param targetPath 目标绝对路径
	 * @throws IOException
	 */
	public static void copyFile(String filePath, String targetPath) throws IOException {
		filePath = changeCompatSeparator(filePath);
		targetPath = changeCompatSeparator(targetPath);
		if (ValidateCommons.isNotEmpty(filePath) && ValidateCommons.isNotEmpty(targetPath)) {
			File file = new File(filePath);
			if (file.exists()) {
				createFolder(cutLastPathNode(targetPath));

				if (filePath.equals(targetPath)) {
					targetPath = addSuffix(targetPath, copyFix);
				}
				FileInputStream in = new FileInputStream(filePath);
				FileOutputStream out = new FileOutputStream(targetPath);
				StreamCommons.binaryTransport(in, out);
			}
		}
	}

	/**
	 * <pre>
	 * 剪切文件
	 * </pre>
	 * @param filePath 文件绝对路径
	 * @param targetPath 目标绝对路径
	 * @throws IOException
	 */
	public static void cutFile(String filePath, String targetPath)
			throws IOException {
		copyFile(filePath, targetPath);
		deleteFile(filePath);
	}

	/**
	 * <pre>
	 * 输出文件
	 * </pre>
	 * @param filePath 文件绝对路径
	 * @param out 输出流
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public static void outputFile(String filePath, OutputStream out)
			throws FileNotFoundException, IOException {
		StreamCommons.binaryTransport(new FileInputStream(filePath), true, out, false);
	}

	/**
	 * <pre>
	 * 打包（zip）输出文件列表
	 * 级联输出文件夹下的文件、文件夹
	 * </pre>
	 * @param fileInfoList 文件列表
	 * @param zipOut zip 输出流
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public static void outputFiles(List<FileInfo> fileInfoList, ZipArchiveOutputStream zipOut)
			throws FileNotFoundException, IOException {
		outputFiles(fileInfoList, zipOut, "");
	}

	/**
	 * <pre>
	 * 打包（zip）输出文件列表
	 * 级联输出文件夹下的文件、文件夹
	 * </pre>
	 * @param fileInfoList 文件列表
	 * @param zipOut zip 输出流
	 * @param parentEntry 条目上级路径
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public static void outputFiles(List<FileInfo> fileInfoList, ZipArchiveOutputStream zipOut, String parentEntry)
			throws FileNotFoundException, IOException {
		if (ValidateCommons.isNotEmpty(fileInfoList)) {
			for (FileInfo fi : fileInfoList) {
				String entryName = fi.getName();
				if (ValidateCommons.isNotEmpty(parentEntry)) {
					entryName = StringCommons.merger(parentEntry, COMPAT_SEPARATOR, entryName);
				}
				String filePath = fi.getPath();
				if (fi.isDirectory()) {
					outputFolder(filePath, zipOut, entryName);
				} else {
					ZipFileCommons.packFile(entryName, new FileInputStream(filePath), zipOut);
				}
			}
		}
	}

	/**
	 * <pre>
	 * 将路径间隔符转换为兼容的（/）间隔符
	 * </pre>
	 * @param path 路径
	 * @param isNormalization 是否标准化路径，（如果存在）去除最后的路径分隔符
	 * @return 兼容的（/）间隔符路径
	 */
	public static String changeCompatSeparator(String path, boolean isNormalization) {
		if (ValidateCommons.isNotEmpty(path)) {
			path = path.replace('\\', '/');
			if (isNormalization && !FileCommons.COMPAT_SEPARATOR.equals(path)) {
				while (path.endsWith(COMPAT_SEPARATOR)) {
					path = path.substring(0, path.length() - 1);
				}
			}
		}
		return path;
	}

	/**
	 * <pre>
	 * [参考 {@link #changeCompatSeparator(String, boolean)}]
	 * 
	 * isNormalization 默认为 true
	 * </pre>
	 */
	public static String changeCompatSeparator(String path) {
		return changeCompatSeparator(path, true);
	}

	/**
	 * <pre>
	 * 获取路径最后一级
	 * </pre>
	 * @param path 路径
	 * @return 路径最后一级
	 */
	public static String getLastPathNode(String path) {
		path = changeCompatSeparator(path);
		int lastNodeBegin = path.lastIndexOf(COMPAT_SEPARATOR) + 1;
		return path.substring(lastNodeBegin);
	}

	/**
	 * <pre>
	 * 去除路径最后一级
	 * </pre>
	 * @param path 路径
	 * @return 去除最后一级后的路径
	 */
	public static String cutLastPathNode(String path) {
		path = changeCompatSeparator(path);
		String lastNode = getLastPathNode(path);
		if (path.equals(lastNode)) {
			path = "";
		} else {
			path = path.substring(0, path.length() - (lastNode.length() + 1));
		}
		return path;
	}

	/**
	 * <pre>
	 * 获取文件扩展名并转换为小写
	 * </pre>
	 * @param filePath 文件绝对路径
	 * @return 小写的文件扩展名
	 */
	public static String getEnlargeName(String filePath) {
		String fileName = getLastPathNode(filePath);
		int begin = fileName.lastIndexOf(".");
		String enlargeName = "";
		if (begin > -1) {
			enlargeName = fileName.substring(begin + 1);
		}
		return enlargeName.toLowerCase();
	}

	/**
	 * <pre>
	 * 为文件添加前缀
	 * 取路径最后一个节点，为节点添加前缀后重新拼接成路径
	 * </pre>
	 * @param filePath 文件绝对路径
	 * @param fixName 前缀名
	 * @return 新路径
	 */
	public static String addPerfix(String filePath, String fixName) {
		String path = cutLastPathNode(filePath);
		String fileName = getLastPathNode(filePath);

		fileName = StringCommons.merger(fixName, fileName);
		return appendPathNode(path, fileName);
	}

	/**
	 * <pre>
	 * 为文件添加前缀
	 * 取路径最后一个节点，为节点（在扩展名前）添加后缀后重新拼接成路径
	 * </pre>
	 * @param filePath 文件绝对路径
	 * @param fixName 后缀名
	 * @return 新路径
	 */
	public static String addSuffix(String filePath, String fixName) {
		String path = cutLastPathNode(filePath);
		String fileName = getLastPathNode(filePath);
		String enlargeName = getEnlargeName(filePath);

		fileName = StringCommons.merger(fileName.substring(0, fileName.length() - enlargeName.length() - 1), fixName, ".", enlargeName);
		return appendPathNode(path, fileName);
	}

	/**
	 * <pre>
	 * 使用兼容的（/）间隔符接路径
	 * </pre>
	 * @param paths 路径集
	 * @return 路径
	 */
	public static String appendPathNode(String... paths) {
		String fullpath = "";
		if (ValidateCommons.isNotEmpty(paths)) {
			StringBuffer pathBuf = new StringBuffer();
			for (int i = 0; i < paths.length; i++) {
				String path = changeCompatSeparator(paths[i]);
				if (i > 0 && !path.startsWith(FileCommons.COMPAT_SEPARATOR)) {
					pathBuf.append(FileCommons.COMPAT_SEPARATOR);
				}
				pathBuf.append(path);
			}
			fullpath = pathBuf.toString();
		}
		return fullpath;
	}

}
